#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <random>
#include <thread>

#include <include/core/SkCanvas.h>
#include <skia_framework.h>

class Rendering {
public:
    enum class Tag : int32_t {
        SwapBuffers = 0,
        CommandIssue = 1,
        SyncUpload = 2,
        Draw = 3,
        MeasureLayout = 4,
        Animation = 5,
        InputHandling = 6,
        VsyncDelay = 7,
        Max,
    };
    friend const Tag operator++(Tag &tag)
    {
        tag = static_cast<Tag>(
            (static_cast<int32_t>(tag) + 1) % static_cast<int32_t>(Tag::Max));
        return tag;
    }

    void UpdateTimestamp(const Tag &tag, int64_t nsts)
    {
        while (currentTag_ != tag) {
            AppendTimestamp(currentTag_, currentTs_);
            ++currentTag_;
        }

        currentTs_ = nsts;
        AppendTimestamp(currentTag_, currentTs_);
        ++currentTag_;
    }

    void SetWH(int32_t width, int32_t height)
    {
        width_ = width;
        height_ = height;
    }

    void Draw(SkCanvas &canvas)
    {
        std::map<Tag, SkColor> colorMap = {
            {Tag::SwapBuffers, 0x3fffff00},
            {Tag::CommandIssue, 0x3fff0000},
            {Tag::SyncUpload, 0x3f00ffff},
            {Tag::Draw, 0x3f0000ff},
            {Tag::MeasureLayout, 0x3f00ff00},
            {Tag::Animation, 0x3f00cc00},
            {Tag::InputHandling, 0x3f009900},
            {Tag::VsyncDelay, 0x3f006600},
        };
        SkPaint paint;
        paint.setAntiAlias(true);
        paint.setStyle(paint.kFill_Style);

        auto heightPerMs = height_ / 5.0 / 16.0;
        auto barWidth = width_ / (queueSize + 2.0);
        auto x = barWidth * (queueSize - 1);
        for (int32_t i = timeQueueByTag_[Tag::SwapBuffers].size() - 1; i >= 0; i--) {
            auto y = height_;
            for (Tag j = Tag::SwapBuffers, k = Tag::CommandIssue; k != Tag::SwapBuffers; ++j, ++k) {
                auto diff = (timeQueueByTag_[k][i] - timeQueueByTag_[j][i]) / 1e6;
                auto diffHeight = diff * heightPerMs;
                paint.setColor(colorMap[j]);
                y -= diffHeight;
                canvas.drawRect(SkRect::MakeXYWH(x, y, barWidth, diffHeight), paint);
            }
            x -= barWidth;
        }

        paint.setColor(0xff00ff00);
        canvas.drawRect(SkRect::MakeXYWH(0, 64.5 * heightPerMs, width_, heightPerMs), paint);
        SkiaFramework::DrawString(canvas, "16ms", 0, 64.5 * heightPerMs);
    }

private:
    void AppendTimestamp(const Tag &tag, int64_t nsts)
    {
        auto &timeQueue = timeQueueByTag_[tag];
        if (timeQueue.size() == queueSize) {
            timeQueue.pop_front();
        }
        timeQueue.push_back(nsts);
    }

    int32_t width_ = 0;
    int32_t height_ = 0;
    Tag currentTag_;
    int64_t currentTs_;
    std::map<Tag, std::deque<int64_t>> timeQueueByTag_;
    static constexpr int32_t queueSize = 60;
};

int main()
{
    std::mutex renderingMutex;
    Rendering rendering;
    rendering.SetWH(500, 500);

    std::thread thread([&]() {
        std::mt19937 mt(0);
        std::normal_distribution<> nd(16.0, 12.0);
        while (true) {
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            int64_t total = std::abs(nd(mt)) * 1e6;
            std::vector<int32_t> vals;
            for (int32_t i = 0; i < static_cast<int32_t>(Rendering::Tag::Max); i++) {
                vals.push_back(std::abs(nd(mt)));
            }

            int32_t valsum = 0;
            for (const auto &val : vals) {
                valsum += val;
            }

            int32_t acc = 0;
            std::lock_guard lock(renderingMutex);
            for (int32_t i = 0; i < static_cast<int32_t>(Rendering::Tag::Max); i++) {
                rendering.UpdateTimestamp(static_cast<Rendering::Tag>(i), total * vals[i] / valsum + acc);
                acc += total * vals[i] / valsum;
            }
        }
    });
    thread.detach();

    SkiaFramework sf;
    sf.SetDrawFunc([&](SkCanvas &canvas) {
        std::lock_guard lock(renderingMutex);
        rendering.Draw(canvas);
    });
    sf.Run();
    return 0;
}
